﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Org.Flowertwig.Media.Services.LocalService.Containers;

namespace Org.Flowertwig.Media.Services.LocalService
{
    public class ArchiveHandler
    {
        protected Archive _archive;
        protected string _sevenZipProgramPath;
        protected bool _compatibleVersion;
        protected string[] _supportedSevenZipVersions = new[] { "4.65", "9.20" };

        protected StringBuilder _unpackOutput = new StringBuilder();

        public ArchiveHandler(string sevenZipPath)
        {
            if (string.IsNullOrEmpty(sevenZipPath))
                throw new ArgumentException("Argument 'sevenZipPath' has to be set.");

            string tmpSevenZipProgramPath = string.Format("{0}{1}7z.exe", sevenZipPath, Path.DirectorySeparatorChar);
            if (!File.Exists(tmpSevenZipProgramPath))
                throw new FileNotFoundException("Argument 'sevenZipPath' is not a valid path to 7-Zip.");

            var p = new Process();
            var startInfo = new ProcessStartInfo(tmpSevenZipProgramPath, "v")
            {
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                UseShellExecute = false
            };

            p.OutputDataReceived += OnVersionCheck;
            p.StartInfo = startInfo;

            var b = p.Start();

            // Start the asynchronous read of the sort output stream.
            p.BeginOutputReadLine();

            p.WaitForExit();

            if (!_compatibleVersion)
            {
                throw new NotSupportedException("7-zip version is not supported. Supported versions are: " + string.Join(",", _supportedSevenZipVersions));
            }
        }

        public double UnpackArchive(string archivePath, string[] filters)
        {
            string id = Hash.CreateId(archivePath);
            string output = string.Format("{0}{2}{1}{2}", Properties.Settings.Default.OutputPath, id,
                                          Path.DirectorySeparatorChar);

            if (!Directory.Exists(output))
            {
                Directory.CreateDirectory(output);
            }

            // TODO: Check if media has been unpacked already.
            if (File.Exists(output + "flowmedia-status.txt"))
            {
                var content = File.ReadAllText(output + "flowmedia-status.txt");
                if (string.Equals("OK", content))
                {
                    return 100; // 100%
                }

                double storedSize;
                if (double.TryParse(content, out storedSize))
                {
                    var directoryInfo = new DirectoryInfo(output);
                    var files = directoryInfo.GetFiles();

                    /******************
                     * Sadly OS is caching data about our file so we need to refresh the information.
                     * http://msdn.microsoft.com/en-us/library/system.io.fileinfo.name.aspx
                     ******************/
                    double size = 0;
                    foreach (var fileInfo in files)
                    {
                        fileInfo.Refresh();
                        size += fileInfo.Length;
                    }
                    //double size = files.Sum(f => f.Length);

                    return (size / storedSize) * 100;
                }
            }

            var archive = ListFilesInArchive(archivePath);
            var unwandedArchive = PlaceUnwantedFilesInSeperateArchive(archive, filters);
            WriteSizeInfo(archive, output);
            var ignoreFileCommands = GetIgnoreFileCommands(unwandedArchive);

            var p = new Process();
            var startInfo =
                new ProcessStartInfo(
                    string.Format("{0}{1}7z.exe", Properties.Settings.Default.SevenZipPath,
                                  Path.DirectorySeparatorChar),
                    string.Format(@"e -o{1} -y{2} {0}", archivePath, output, ignoreFileCommands))
                    {
                        UseShellExecute = true
                    };

            p.StartInfo = startInfo;
            p.Start();

            return 0;
        }

        private static void WriteSizeInfo(Archive archive, string output)
        {
            var size = archive.Items.Sum(archiveItem => archiveItem.Size);
            File.WriteAllText(output + "flowmedia-status.txt", size.ToString());
        }

        private static Archive PlaceUnwantedFilesInSeperateArchive(Archive archive, IEnumerable<string> filters)
        {
            var items = new List<ArchiveItem>(archive.Items);
            var unwantedArchive = new Archive();

            // Yes, i know. This is backwards. (Ignoring files instead of just take the ones we want).
            foreach (var archiveItem in items)
            {
                var name = archiveItem.Name;
                var isMatch = filters.Any(filter => Regex.IsMatch(name, filter));

                if (!isMatch)
                {
                    archive.Items.Remove(archiveItem);
                    unwantedArchive.Items.Add(archiveItem);
                }
            }
            return unwantedArchive;
        }

        private static string GetIgnoreFileCommands(Archive archive)
        {
            // Yes, i know. This is backwards. (Ignoring files instead of just take the ones we want).
            var sbIgnoreFileCommands = new StringBuilder();
            foreach (var archiveItem in archive.Items)
            {
                sbIgnoreFileCommands.Append(" -x!" + archiveItem.Name);
            }
            return sbIgnoreFileCommands.ToString();
        }

        public void SetUnpackInformation(string archivePath, string outputDir)
        {
            StringBuilder sb = new StringBuilder();
            var archive = ListFilesInArchive(archivePath);

            foreach (var archiveItem in archive.Items)
            {
                sb.AppendLine(string.Format("{0}:{1}", archiveItem.Size, archiveItem.Name));
            }

            File.WriteAllText(outputDir + "flowmedia-status.txt", sb.ToString());
        }

        public int TryGetUnpackStatus(string archivePath, out int processProcentage)
        {
            processProcentage = 0;

            string id = Hash.CreateId(archivePath);
            string output = string.Format("{0}{2}{1}{2}", Properties.Settings.Default.OutputPath, id,
                                          Path.DirectorySeparatorChar);

            if (!File.Exists(output + "flowmedia-status.txt"))
            {
                return -1;
            }
            else
            {


                return 0;
            }
        }

        public Archive ListFilesInArchive(string archivePath)
        {
            _archive = new Archive { Name = archivePath };

            var p = new Process();
            var startInfo = new ProcessStartInfo(string.Format("{0}{1}7z.exe", Properties.Settings.Default.SevenZipPath, Path.DirectorySeparatorChar), string.Format(@"l {0}", archivePath))
            {
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                UseShellExecute = false
            };

            p.OutputDataReceived += OnOutputDataReceived;
            p.StartInfo = startInfo;

            p.Start();

            // Start the asynchronous read of the sort output stream.
            p.BeginOutputReadLine();

            p.WaitForExit();

            return _archive;
        }

        void OnVersionCheck(object sender, DataReceivedEventArgs e)
        {
            var content = e.Data;
            if (string.IsNullOrEmpty(content))
                return;

            var split = content.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var s in split)
            {
                if (_supportedSevenZipVersions.Contains(s))
                    _compatibleVersion = true;
            }
        }


        void Unpack_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            _unpackOutput.AppendLine(e.Data);
        }

        void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            // TODO: Make this less ugly.

            string content = e.Data;

            if (string.IsNullOrEmpty(content))
                return;

            content = content.Trim();

            if (content.StartsWith("7-Zip"))
                return;

            if (content.StartsWith("Listing"))
                return;

            if (content.StartsWith("Solid"))
                return;

            if (content.StartsWith("Blocks"))
                return;

            if (content.StartsWith("Multivolume"))
                return;

            if (content.StartsWith("Volumes"))
                return;

            if (content.StartsWith("-"))
                return;

            if (content.StartsWith("Date"))
                return;

            //if (content.StartsWith("Volumes"))
            //    return;

            if (content.EndsWith("folders"))
            {
                // Get total size for all files in archive.

                return;
            }

            var columns = content.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            /*
                 * columns[0]   =   date
                 * columns[1]   =   time
                 * columns[2]   =   attributes
                 * columns[3]   =   uncompressed file size
                 * columns[4]   =   compressed file size
                 * columns[5]   =   file name
                 */

            if (columns.Length == 6)
            {
                var name = columns[5];
                long size;
                if (long.TryParse(columns[3], out size))
                {
                    _archive.Items.Add(new ArchiveItem
                                           {
                                               Name = name,
                                               Size = size
                                           });
                }
            }

            // Get filename and size for video
            //File.WriteAllText(
            //    string.Format(@"{0}{1}{2}.log", Properties.Settings.Default.OutputPath,
            //                  Path.DirectorySeparatorChar, id), content);
        }
    }
}
